package model.handler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import java.util.List;

import model.Executable;
import model.GameClock;
import model.entity.Avatar;
import model.entity.BasicEntity;
import model.entity.Entity;
import model.entity.Mountable;
import model.entity.stats.StatType;
import model.interactionhandler.InteractionContainer;
import model.item.Item;
import model.map.Direction;
import model.map.GameMap;
import model.map.HexLocationProjection;
import model.map.Location;
import model.map.LocationProjection;
import model.map.MovementType;
import model.map.Terrain;
import model.map.World;
import model.resource.AvatarMount;
import main.Log;
import utilities.*;


/**
 * 
 *
 * This is the class that handles the movement of 
 * BasicEntities on the Map.
 * 
 */

public class MovementHandler implements Archiveable {

	private static MovementHandler instance = null;

	private GameMap currentMap;
	private LocationProjection moveLogic;
	private Map<BasicEntity, Boolean> isMoving;
	private World w = World.getInstance();

	public static MovementHandler getInstance() {
		if( instance != null ) { return instance; }
		return instance = new MovementHandler();
	}

	private MovementHandler(){
		this.currentMap = w.getCurrentMap();
		this.isMoving = new HashMap<BasicEntity, Boolean>();
		this.moveLogic = new HexLocationProjection();
	}

	public boolean isEntityMoving(BasicEntity e){
		if( ! isMoving.containsKey(e) ) { isMoving.put(e, false); }
		return isMoving.get(e).booleanValue();
	}

	public void moveEntity(final BasicEntity e, Direction d){
		// check to see if entity can move
		if(!isEntityMoving(e)){

			e.setDirection(d);
			//Log.out(Log.MODEL, "entity can move");

			List<MovementType> entityMovementTypes 	= e.getMovementTypes();
			if( entityMovementTypes == null ) { return ; }
			Location pLocation 	= moveLogic.projectLocation(e.getLocation(), d);
			Terrain pTerrain 		= currentMap.getTerrain(pLocation);
			AvatarMount avatarMount = AvatarMount.getInstance();
			double entitySpeed; 
			long tickCount;

			// check to see if can move to the desired(projected) location
			if(entityMovementTypes.contains(pTerrain.getMovementType()) && !currentMap.hasObstacle(pLocation)){
				//Log.out(Log.MODEL, "able to move to the desired location");

				// check to see if there is a basic_entity in projected location
				if(currentMap.hasEntity(pLocation) || currentMap.hasMountable(pLocation)){
					//Log.out(Log.MODEL, "there is an entity there");

					BasicEntity pEntity; 
					if (currentMap.hasEntity(pLocation))
						pEntity = currentMap.getEntity(pLocation);
					else //TODO design flaw is forcing us to do this. We will try to fix it if we have time.
						pEntity = (BasicEntity)currentMap.getMountable(pLocation);

					// check if the moving entity is the Avatar
					// doesn't break OCP. Avatar will be the only entity ever
					// that makes the interaction menu pop up 
					if(isAvatar(e) && !(World.getInstance().isInBattle())){
						//Log.out(Log.MODEL, "not in battle map");
						InteractionContainer.getInstance().startInteraction((Avatar)e, pEntity);
					}
				}
				else{
					//move entity and (if availiable) handle the item
					e.setLocation(pLocation);
					if( this.isAvatar(e) ) {
						World.getInstance().getCurrentMap().updateFodOfWar();
					}
					//Log.out(Log.MODEL, "new location set");

					//if(currentMap.hasAreaEffect(pLocation){}
					if(currentMap.hasItem(pLocation)){
						//Log.out(Log.MODEL, "remove item and touch it");
						Item currentItem = currentMap.removeItem(pLocation);
						currentItem.touch(e);
					}
					//Log.out(Log.MODEL, "WANTS TO BE avatar is mounteddddddddddd" + isMounted(e));
					//check if entity is mounted (if so, update speed)
					if(avatarMount.isMounted(World.getInstance().getPlayer())){
						//Log.out(Log.MODEL, "avatar is mounteddddddddddd");
						entitySpeed = avatarMount.getMountableSpeed(World.getInstance().getPlayer());
						Mountable m = avatarMount.getMount(World.getInstance().getPlayer());
						m.setLocation(pLocation);
						m.setDirection(d);
						e.setLocation(pLocation);
						e.setDirection(d);
						
					}
					else{
						//Log.out(Log.MODEL, "avatar is not mounted");
						entitySpeed = e.getStatVal(StatType.MOVEMENTSPEED);
					}

					tickCount = speedToTick(entitySpeed);

					// TODO: double check the placement of next 6 lines
					isMoving.put(e, true);
					GameClock.getInstance().schedule(tickCount, new Executable() {
						public void execute(){
							//Log.out(Log.MODEL, "isMoving set to false");
							isMoving.put(e, false);
						}
					});

				}
			}
		}
	}

	//TODO: tweak this later if needed
	//TODO: we have to take into account if it is mounted. 
	private long speedToTick(double speed){
		return (long)(1/speed);
	}

	//private void mountedMove(BasicEntity e, Location pLocation){

	//}

	private boolean isAvatar(BasicEntity e){
		if(e == w.getPlayer()){
			return true;
		}
		else{
			return false;
		}
	}
	
	
	public Archive save() {
		Archive archive = ArchiveFactory.createArchive();
		
//		archive.putClassName("moveLogic", this.moveLogic.getClass().getName());
//		archive.putOneRelationship("moveLogic", this.moveLogic);
//		
//		List<String> isMovingClassList = new ArrayList<String>();
//		List<BasicEntity> isMovingKeys = new ArrayList<BasicEntity>(this.isMoving.keySet());
//		List<String> isMovingValues = new ArrayList<String>();
//		for (int i = 0; i < isMovingKeys.size(); i++) {
//			BasicEntity entity = isMovingKeys.get(i);
//			isMovingClassList.add(entity.getClass().getName());
//			isMovingValues.add(Boolean.toString(this.isMoving.get(entity)));
//		}
//		archive.putAttributeList("isMovingClassList", isMovingClassList);
//		archive.putManyRelationship("isMovingKeys", isMovingKeys);
//		archive.putAttributeList("isMovingValues", isMovingValues);
		
		return archive;
	}
	
	public void load(Archive archive) {
//		this.moveLogic = (LocationProjection)ModelFactory.getInstance().createInstanceWithClassName(archive.getClassName("moveLogic"));
//		this.moveLogic.load(archive.getOneRelationship("moveLogic"));
//		
//		List<String> isMovingClassList = archive.getAttributeList("isMovingClassList");
//		List<Archive> isMovingKeys = archive.getManyRelationship("isMovingKeys");
//		List<String> isMovingValues = archive.getAttributeList("isMovingValues");
//		this.isMoving = new HashMap<BasicEntity, Boolean>();
//		for (int i = 0; i < isMovingClassList.size(); i++) {
//			BasicEntity entity = (BasicEntity)ModelFactory.getInstance().createInstanceWithClassName(isMovingClassList.get(i));
//			entity.load(isMovingKeys.get(i));
//			Boolean value = Boolean.parseBoolean(isMovingValues.get(i));
//			this.isMoving.put(entity, value);
//		}
	}

}
